Completed
Push — master ( c898d2...a76502 )
by Rafael S.
08:20
created

Interpolator.sinc   A

Complexity

Conditions 4

Size

Total Lines 13
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 13
rs 9.85
c 0
b 0
f 0
cc 4
1
/*
2
 * Copyright (c) 2019 Rafael da Silva Rocha.
3
 * Copyright 2012 Spencer Cohen
4
 *
5
 * Permission is hereby granted, free of charge, to any person obtaining
6
 * a copy of this software and associated documentation files (the
7
 * "Software"), to deal in the Software without restriction, including
8
 * without limitation the rights to use, copy, modify, merge, publish,
9
 * distribute, sublicense, and/or sell copies of the Software, and to
10
 * permit persons to whom the Software is furnished to do so, subject to
11
 * the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be
14
 * included in all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
 *
24
 */
25
26
/**
27
 * @fileoverview The Interpolator class. Based on Smooth.js by Spencer Cohen.
28
 * @see https://github.com/rochars/wavefile
29
 * @see https://github.com/osuushi/Smooth.js
30
 */
31
32
/**
33
 * A class to get scaled values out of arrays.
34
 * @extends WaveFileReader
35
 */
36
export class Interpolator {
37
  
38
  /**
39
   * @param {number} scaleFrom the length of the original array.
40
   * @param {number} scaleTo The length of the new array.
41
   * @param {?Object} details The extra configuration, if needed.
42
   */
43
  constructor(scaleFrom, scaleTo, details) {
44
    /**
45
     * The length of the original array.
46
     * @type {number}
47
     */
48
    this.length_ = scaleFrom;
49
    /**
50
     * The scaling factor.
51
     * @type {number}
52
     */
53
    this.scaleFactor_ = (scaleFrom - 1) / scaleTo;
54
    /**
55
     * The interpolation function.
56
     * @type {Function}
57
     */
58
    this.interpolate = this.sinc;
59
    if (details.method === 'point') {
60
    	this.interpolate = this.point;
61
    } else if(details.method === 'linear') {
62
    	this.interpolate = this.linear;
63
    } else if(details.method === 'cubic') {
64
    	this.interpolate = this.cubic;
65
    }
66
    /**
67
     * The clipping function.
68
     * @type {Function}
69
     */
70
    this.clip_ = clipClamp_;
71
    // The clip function
72
    if (details.clip === 'periodic') {
73
      this.scaleFactor_ = scaleFrom / scaleTo;
74
      this.clip_ = clipPeriodic_;
75
    } else if (details.clip === 'mirror') {
76
      this.clip_ = clipMirror_;
77
    }
78
    /**
79
     * The tanget factor for cubic interpolation.
80
     * @type {number}
81
     */
82
    this.tangentFactor_ = 1 - Math.max(0, Math.min(1, details.tension || 0));
83
    // Configure the kernel for sinc
84
    /**
85
     * The sinc filter size.
86
     * @type {number}
87
     */
88
    this.sincFilterSize_ = details.sincFilterSize || 1;
89
    /**
90
     * The sinc kernel.
91
     * @type {Function}
92
     */
93
    this.kernel_ = sincKernel_(details.sincWindow || gaussianWindow_);
94
    if (details.method === 'lanczos') {
95
      this.kernel_ = sincKernel_(lanczosWindow_(details.lanczosFilterSize));
96
      this.sincFilterSize = details.lanczosFilterSize;
97
    }
98
  }
99
100
  /**
101
   * @param {number} t The index to interpolate.
102
   * @param {Array|TypedArray} samples the original array.
103
   * @return {number} The interpolated value.
104
   */
105
  point(t, samples) {
106
    return this.getClippedInput_(Math.round(this.scaleFactor_ * t), samples);
107
  }
108
109
  /**
110
   * @param {number} t The index to interpolate.
111
   * @param {Array|TypedArray} samples the original array.
112
   * @return {number} The interpolated value.
113
   */
114
  linear(t, samples) {
115
    t = this.scaleFactor_ * t;
116
    let k = Math.floor(t);
117
    t -= k;
118
    return (1 - t) *
119
    	this.getClippedInput_(k, samples) + t *
120
    	this.getClippedInput_(k + 1, samples);
121
  }
122
123
  /**
124
   * @param {number} t The index to interpolate.
125
   * @param {Array|TypedArray} samples the original array.
126
   * @return {number} The interpolated value.
127
   */
128
  cubic(t, samples) {
129
    t = this.scaleFactor_ * t;
130
    let k = Math.floor(t);
131
    let m = [this.getTangent_(k, samples), this.getTangent_(k + 1, samples)];
132
    let p = [this.getClippedInput_(k, samples),
133
      this.getClippedInput_(k + 1, samples)];
134
    t -= k;
135
    let t2 = t * t;
136
    let t3 = t * t2;
137
    return (2 * t3 - 3 * t2 + 1) *
138
      p[0] + (t3 - 2 * t2 + t) *
139
      m[0] + (-2 * t3 + 3 * t2) *
140
      p[1] + (t3 - t2) * m[1];
141
  }
142
143
  /**
144
   * @param {number} t The index to interpolate.
145
   * @param {Array|TypedArray} samples the original array.
146
   * @return {number} The interpolated value.
147
   */
148
  sinc(t, samples) {
149
    t = this.scaleFactor_ * t;
150
    let k = Math.floor(t);
151
    let ref = k - this.sincFilterSize_ + 1;
152
    let ref1 = k + this.sincFilterSize_;
153
    let sum = 0;
154
    for (let n = ref, j = ref;
155
        ref <= ref1 ? j <= ref1 : j >= ref1; 
156
        n = ref <= ref1 ? ++j : --j) {
157
      sum += this.kernel_(t - n) * this.getClippedInput_(n, samples);
158
    }
159
    return sum;
160
  }
161
162
  /**
163
   * @param {number} k The scaled index to interpolate.
164
   * @param {Array|TypedArray} samples the original array.
165
   * @return {number} The tangent.
166
   * @private
167
   */
168
  getTangent_(k, samples) {
169
    return this.tangentFactor_ *
170
      (this.getClippedInput_(k + 1, samples) -
171
        this.getClippedInput_(k - 1, samples)) / 2;
172
  }
173
174
  /**
175
   * @param {number} t The scaled index to interpolate.
176
   * @param {Array|TypedArray} samples the original array.
177
   * @return {number} The interpolated value.
178
   * @private
179
   */
180
  getClippedInput_(t, samples) {
181
    if ((0 <= t && t < this.length_)) {
182
      return samples[t];
183
    }
184
    return samples[this.clip_(t, this.length_)];
185
  }
186
}
187
188
// Sinc functions
189
190
/**
191
 * The default window function.
192
 * @param {number} x The sinc signal.
193
 * @return {number}
194
 * @private
195
 */
196
function gaussianWindow_(x) {
197
  return Math.exp(-x * x);
198
}
199
200
/**
201
 * @param {Function} window The window function.
202
 * @return {Function}
203
 * @private
204
 */
205
function sincKernel_(window) {
206
  return function(x) { return sinc_(x) * window(x); };
207
}
208
209
/**
210
 * @param {number} size The filter size.
211
 * @return {Function}
212
 * @private
213
 */
214
function lanczosWindow_(size) {
215
  return function(x) { return sinc_(x / size); };
216
}
217
218
/**
219
 * @param {number} x The sinc signal.
220
 * @return {number}
221
 * @private
222
 */
223
function sinc_(x) {
224
  if (x === 0) {
225
    return 1;
226
  }
227
  return Math.sin(Math.PI * x) / (Math.PI * x);
228
}
229
230
// Clip functions
231
232
/**
233
 * @param {number} t The scaled index
234
 * @param {number} n The size of the original array
235
 * @return {number}
236
 * @private
237
 */
238
function clipClamp_(t, n) {
239
  return Math.max(0, Math.min(t, n - 1));
240
}
241
242
/**
243
 * @param {number} t The scaled index
244
 * @param {number} n The size of the original array
245
 * @return {number}
246
 * @private
247
 */
248
function clipPeriodic_(t, n) {
249
  t = t % n;
250
  if (t < 0) {
251
    t += n;
252
  }
253
  return t;
254
}
255
256
/**
257
 * @param {number} t The scaled index
258
 * @param {number} n The size of the original array
259
 * @return {number}
260
 * @private
261
 */
262
function clipMirror_(t, n) {
263
  let period = 2 * (n - 1);
264
  t = clipPeriodic_(t, period);
265
  if (t > n - 1) {
266
    t = period - t;
267
  }
268
  return t;
269
}
270